package org.ascollada.core
{
	import flash.errors.IllegalOperationError;
	import flash.events.EventDispatcher;
	import flash.events.TimerEvent;
	import flash.utils.ByteArray;
	import flash.utils.Timer;
	import flash.utils.getTimer;
	
	import org.ascollada.core.ns.collada;
	import org.ascollada.events.DaeFileEvent;
	import org.ascollada.events.DaeParseEvent;
	import org.ascollada.fx.DaeEffect;
	import org.ascollada.fx.DaeMaterial;
	import org.ascollada.io.DaeFileLoader;
	import org.ascollada.io.DaeImageLoaderThread;
	import org.ascollada.utils.DaeLogger;
	import org.libspark.thread.EnterFrameThreadExecutor;
	import org.libspark.thread.Thread;
	
	/**
	 * 
	 * @author Tim Knip
	 */ 
	public class DaeDocument extends EventDispatcher
	{
		use namespace collada;
  
		public var COLLADA :XML;
		
		public var filename :String;
		public var baseUrl :String;
		public var nodes :Array;
		public var animations :Array;
		public var animationClips :Array;
		public var animatables :Object;
		public var sources :Object;
		public var geometries :Object;
		public var materials :Object;
		public var effects :Object;
		public var images :Object;
		public var library_nodes :Object;
		
		private var _searchPaths :Array;

		private var _ids :XMLList;
		private var _parseInterval :int = 10;
		private var _parseSpeed :int = 10;
		private var _queue :Array;
		
		private var _startTime :int;
		
		/**
		 * 
		 */ 
		public function DaeDocument(parseInterval:int=10, parseSpeed:int=100)
		{
			_parseInterval = parseInterval;
			_parseSpeed = parseSpeed;
			_searchPaths = new Array();
			
			addSearchPath(".");
			addSearchPath("images");
			addSearchPath("../images");
			addSearchPath("image");
			addSearchPath("../image");
			addSearchPath("assets");
			addSearchPath("../assets");
			
			Thread.initialize(new EnterFrameThreadExecutor());
		}
		
		/**
		 * 
		 * @param path
		 */ 
		public function addSearchPath(path:String):void
		{	
			if(path.charAt(path.length-1) == "/")
				path = path.substr(0, path.length-1);
			if(_searchPaths.indexOf(path) == -1)
				_searchPaths.push(path);
		}
		
		public function findAnimationClipsForAnimation(id:String):Array
		{
			var result : Array = new Array();
			for each(var clip:DaeAnimationClip in animationClips)
			{
				var pos : int = clip.animations.indexOf(id);
				if(pos >= 0) 
				{
					result.push(clip);
				}
			}	
			return result;
		}
		
		/**
		 * 
		 * @param asset
		 */ 
		public function parse(asset:*):void
		{
			_startTime = getTimer();
			
			this.baseUrl = "";
			this.filename = "";
			this.animatables = new Object();
			this.images = new Object();
			
			if(asset is XML)
			{
				parseXML(asset as XML);
			}
			else if(asset is ByteArray)
			{
				parseXML(new XML(asset));
			}
			else if(asset is String)
			{
				this.filename = String(asset);
				this.filename = this.filename.replace(/\\/g, "/");
				
				var parts :Array = this.filename.split("/");
				parts.pop();
				
				this.baseUrl = parts.join("/");
				
				DaeLogger.log("Loading COLLADA from url : " + this.filename);
				
				var loader :DaeFileLoader = new DaeFileLoader();
				
				loader.addEventListener(DaeFileEvent.LOAD_COMPLETE, onColladaLoadSuccess);
				loader.addEventListener(DaeFileEvent.LOAD_PROGRESS, onColladaLoadProgress);
				loader.addEventListener(DaeFileEvent.LOAD_ERROR, onColladaLoadError);
				
				loader.load(this.filename);
			}
			else
			{
				throw new IllegalOperationError("");
			}
		}
		
		/**
		 * 
		 */ 
		protected function parseLibraryAnimationClips():void
		{
			var list :XMLList = this.COLLADA.library_animation_clips..animation_clip;
			
			this.animationClips = new Array();
			
			for each(var element:XML in list)
			{
				var clip : DaeAnimationClip = new DaeAnimationClip(this, element);
				
				this.animationClips.push(clip);
			}
		}
		
		/**
		 * 
		 */ 
		protected function parseLibraryAnimations():void
		{
			var list :XMLList = this.COLLADA.library_animations..animation;

			this.animations = new Array();
			
			for each(var element:XML in list)
			{
				if(element.channel.length() == 0 || element.channel.length() != element.sampler.length())
				{
					continue;
				}
					
				var animation :DaeAnimation = new DaeAnimation(this, element);
				
				var clips : Array = findAnimationClipsForAnimation(animation.id);
				
				if(clips.length)
				{
					trace("skipping " + animation.id);
				//	continue;	
				}
				else
				{
				//	continue;
				}
				
				if(animation.channels && animation.channels.length)
				{
					for each(var channel:DaeChannel in animation.channels)
					{
						if(!(animatables[channel.targetID] is Array))
							animatables[channel.targetID] = new Array();
						
						if(channel.sampler.input && channel.sampler.input.length)
						{
							animatables[channel.targetID].push(channel);
						}
						else
						{
							DaeLogger.log("[WARNING] channel defined without any data : " + channel);
						}
					}
					
					this.animations.push(animation);
				}
			}
			
			DaeLogger.log(" - #animations: " + this.animations.length);
		}
		
		/**
		 * 
		 */ 
		protected function parseLibraryEffects():void
		{
			var list :XMLList = this.COLLADA.library_effects..effect;
			var element :XML;
			
			this.effects = new Object();
			
			for each(element in list)
			{
				var effect :DaeEffect = new DaeEffect(this, element);
				this.effects[ effect.id ] = effect;
			}
		}
		
		/**
		 * 
		 */ 
		protected function parseLibraryGeometries():void
		{
			var list :XMLList = this.COLLADA.library_geometries..geometry;
			var element :XML;
			
			this.geometries = new Object();
			
			for each(element in list)
			{
				var geometry :DaeGeometry = new DaeGeometry(this, element);
				
				this.geometries[geometry.id] = geometry;
			}
		}
		
		/**
		 * 
		 */ 
		protected function parseLibraryImages():void
		{
			var list :XMLList = this.COLLADA.library_images..image;
			var element :XML;
			var hasImages :Boolean = false;
			
			this.images = new Object();
			
			var loader :DaeImageLoaderThread = new DaeImageLoaderThread(this.baseUrl);
			
			loader.addEventListener(DaeParseEvent.IMAGES_COMPLETE, onImagesComplete);
			
			for each(element in list)
			{
				var image :DaeImage = new DaeImage(this, element);
				if(image.init_from && image.init_from.length)
				{
					this.images[ image.id ] = image;
					
					loader.addImage(image);
					
					hasImages = true;
				}
			}
			
			if(hasImages)
				loader.start();
			else
				onImagesComplete();
		}
		
		/**
		 * 
		 */ 
		protected function parseLibraryMaterials():void
		{
			var list :XMLList = this.COLLADA.library_materials..material;
			var element :XML;
			
			this.materials = new Object();
			
			for each(element in list)
			{
				var material :DaeMaterial = new DaeMaterial(this, element);
				this.materials[ material.id ] = material;
			}
		}
		
		/**
		 * 
		 */ 
		protected function parseLibraryNodes():void
		{
			var list :XMLList = this.COLLADA.library_nodes..node;
			var element :XML;
			
			this.library_nodes = new Object();
			
			for each(element in list)
			{
				var node:DaeNode = new DaeNode(this, element);
				this.library_nodes[ node.id ] = node;
			}
		}
		
		/**
		 * 
		 */ 
		protected function parseScene():void
		{
			var element :XML = this.COLLADA.scene.instance_visual_scene[0];
			
			if(!element)
			{
				DaeLogger.log("[ERROR] could not find a scene!");
				return;
			}
			
			var url :String = element.@url.toString().split("#")[1];
			
			var scene :XML = this.COLLADA..visual_scene.(@id == url)[0];
			
			if(!scene)
			{
				DaeLogger.log("[ERROR] could not find a scene with id = '" + url + "'!");
				return;
			}
			
			// parse scenegraph
			this.nodes = new Array();
			var list :XMLList = scene.node;
			for(var i:int = 0; i < list.length(); i++)
			{
				this.nodes.push(new DaeNode(this, list[i]));
			}
			
			
			// extra profiles
			
			
		}
		
		/**
		 * 
		 */ 
		protected function parseXML(xml:XML):void
		{
			this.COLLADA = xml;
			
			DaeLogger.log("Starting parse of COLLADA file.");
			
			_ids = this.COLLADA..*.(hasOwnProperty("@id"));
			
			_queue = new Array();
			
			// parse all <source> elements which have an @id attribute
			this.sources = new Object();
			var list :XMLList = this.COLLADA..source.(hasOwnProperty("@id"));
			for each(var element:XML in list)
			{
				_queue.push(element);
			}
				
			var timer :Timer = new Timer(_parseInterval);
			timer.addEventListener(TimerEvent.TIMER, onParseNextSource);
			timer.start();
		}
		
		/**
		 * 
		 */ 
		private function onLoadNextImage(e:TimerEvent=null):void
		{
			if(_queue.length)
			{
				
			}
			else
			{
				
			}
		}
		
		/**
		 * 
		 */ 
		private function onParseNextSource(e:TimerEvent=null):void
		{
			if(_queue.length)
			{
				for(var i:int = 0; i < _parseSpeed; i++)
				{
					var element :XML = _queue.pop() as XML;
					
					var src :DaeSource = new DaeSource(this, element);
					
					this.sources[src.id] = src;
					
					if(_queue.length == 0) break;
				}
			}	
			else
			{
				var timer :Timer = e.target as Timer;
				
				timer.stop();
				timer.removeEventListener(TimerEvent.TIMER, onParseNextSource);
				
				// order *is* important
				parseLibraryMaterials();
				parseLibraryEffects();
				parseLibraryAnimationClips();
				parseLibraryAnimations();
				parseLibraryGeometries();
				parseLibraryNodes();
				parseScene();
			
				dispatchEvent(new DaeFileEvent(DaeFileEvent.LOAD_COMPLETE, this.filename, ""));
				
				var elapsed :Number = (getTimer() - _startTime) / 1000;
				
				DaeLogger.log("Parse of COLLADA file took " + elapsed.toFixed(1) + " seconds.");
			}
		}
		
		private function onColladaLoadError(e:DaeFileEvent):void
		{	
		}
		
		private function onColladaLoadProgress(e:DaeFileEvent):void
		{	
		}
		
		private function onColladaLoadSuccess(e:DaeFileEvent):void
		{	
			var loader :DaeFileLoader = e.target as DaeFileLoader;
			
			this.COLLADA = new XML(loader.data);
			
			// first try to load our images.
			parseLibraryImages();
		}
		
		private function onImagesComplete(e:DaeParseEvent=null):void
		{
			parseXML(this.COLLADA);
		}
	}
}